#include "unzip_programfile.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <fcntl.h>

#ifdef _WIN32
# include <direct.h>
# include <io.h>
#else
# include <unistd.h>
# include <utime.h>
#endif
#include <QDebug>


static const auto g_noDotAndLinksfilter = QDir::NoDotAndDotDot | QDir::NoSymLinks;
static const int g_maxFloderCount = 4;


#define CASESENSITIVITY (0)
#define WRITEBUFFERSIZE (8192)
#define MAXFILENAME (256)

#ifdef __APPLE__
// In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for specific 64 bit functions
#define FOPEN_FUNC(filename, mode) fopen(filename, mode)
#define FTELLO_FUNC(stream) ftello(stream)
#define FSEEKO_FUNC(stream, offset, origin) fseeko(stream, offset, origin)
#else
#define FOPEN_FUNC(filename, mode) fopen64(filename, mode)
#define FTELLO_FUNC(stream) ftello64(stream)
#define FSEEKO_FUNC(stream, offset, origin) fseeko64(stream, offset, origin)
#endif

bool UnzipProgramFile::writeFile(const QString &filePath,
                                 const QByteArray &content,
                                 QString &error)
{
    QFile file(QString(m_filePath + filePath).toUtf8());
    if (filePath.contains("/")) {
        // 1. 如果要添加的文件是在Program的子目录下, 则要检查目录存在性及是否超过4个目录
        // 文件目录不存在则创建目录，如果目录超过4个则失败
        if (!isAllDirOrFile(m_filePath, true)) {
            error = u8"分离机Program直接根目录下存在文件，不能创建包含子文件夹路径的文件!";
            qDebug() << "error: " << error;
            return false;
        }
        QString dirPath = m_filePath + filePath.left(filePath.indexOf('/'));
        QDir dir(dirPath.toUtf8());
        if (!dir.exists()) {
            QDir rootDir(m_filePath);
            // 存在.和..目录
            auto len = rootDir.entryList(QDir::Dirs | g_noDotAndLinksfilter).length();
            if (len >= g_maxFloderCount) {
                error = u8"分离机已经存在四个目录，无法创建新目录!";
                qDebug() << "error: " << error;
                return false;
            }
            if (!dir.mkpath(dirPath.toUtf8())) {
                error = u8"分离机上新目录创建失败!";
                qDebug() << "error: " << error;
                return false;
            }
        }
    } else {
        // 2. 判断要添加的文件是否是Program直接路径下?
        // 如果添加文件是在Program目录下则要检查Program下是否全是文件不存在目录，有文件夹则添加失败
        // 如果Program下全是文件，则直接添加
        if (!isAllDirOrFile(m_filePath, false)) {
            error = u8"分离机Program目录下存在文件夹，不能直接在根目录增加制备文件!";
            qDebug() << "error: " << error;
            return false;
        }
    }
    if (file.open(QIODevice::WriteOnly)) {
        file.write(content);
        file.close();
        return true;
    } else{
        error = QString(u8"指定的文件:%1无法创建!").arg(filePath);
        qDebug() << "error: " << error;
        return false;
    }
}

bool UnzipProgramFile::isAllDirOrFile(const QString &path, const bool &isAllDir)
{
    QDir dir(path);
    // program目录为空，则可以随意添加文件夹或文件，因此返回空
    auto filter = QDir::AllDirs | QDir::Files | g_noDotAndLinksfilter;
    if (dir.entryList(filter).length() <= 0) {
        return true;
    }
    if (isAllDir) {
        // 全文件夹
        if (dir.entryList(g_noDotAndLinksfilter | QDir::Files).length() > 0) {
            qDebug() << "error: " << u8"目录下全是文件夹";
            return false;
        }
        return true;
    } else {
        // 全文件
        if (dir.entryList(g_noDotAndLinksfilter | QDir::AllDirs).length() > 0) {
            qDebug() << "error: " << u8"目录下全是文件";
            return false;
        }
        return true;
    }
}

UnzipProgramFile::UnzipProgramFile()
{

}

void UnzipProgramFile::doExtractProgramZipFile(const QString &path, const QString &outPath)
{
    unzFile uf;
    m_filePath = outPath;
    uf = unzOpen64(path.toUtf8().data());
    if (uf != nullptr) {
        this->do_extract(uf);
        unzClose(uf);
    }
}

int UnzipProgramFile::do_extract(unzFile uf)
{
    uLong i;
    unz_global_info64 gi;
    int err;

    err = unzGetGlobalInfo64(uf,&gi);
    if (err!=UNZ_OK)
        printf("error %d with zipfile in unzGetGlobalInfo \n",err);

    for (i=0;i<gi.number_entry;i++)
    {
//        if (do_extract_currentfile(uf) != UNZ_OK)
//            break;
        do_extract_currentfile(uf);

        if ((i+1)<gi.number_entry)
        {
            err = unzGoToNextFile(uf);
            if (err!=UNZ_OK)
            {
                printf("error %d with zipfile in unzGoToNextFile\n",err);
                break;
            }
        }
    }

    return 0;
}

bool UnzipProgramFile::do_extract_currentfile(unzFile uf)
{
    char filename_inzip[256];
    int err=UNZ_OK;
    void* buf = nullptr;
    uInt size_buf = WRITEBUFFERSIZE ;

    unz_file_info64 file_info;
    err = unzGetCurrentFileInfo64(uf,
                                  &file_info,
                                  filename_inzip,
                                  sizeof(filename_inzip),
                                  NULL, 0, NULL, 0);
    if (err != UNZ_OK) {
        qDebug() << "error: " << err << " with zipfile in unzGetCurrentFileInfo";
        return false;
    }
    buf = (void *)malloc(size_buf);
    err = unzOpenCurrentFilePassword(uf, nullptr);
    if (err != UNZ_OK) {
        qDebug() << "error: " << err << " with zipfile in unzOpenCurrentFilePassword";
        return false;
    }
    err = unzReadCurrentFile(uf, buf, size_buf);
    if (err < 0) {
        qDebug() << "error: " << err << " with zipfile in unzReadCurrentFile";
        return false;
    }

    QString error;
    QByteArray tmpBuf(reinterpret_cast<char*>(buf), err);
    QString filePath = QString::fromUtf8(filename_inzip);
    qDebug() << "filename_inzip: " << filename_inzip;

    this->writeFile(filePath, tmpBuf, error);
    free(buf);
    return true;
}
